package com.android.yummycookies.asyncTask;

import java.lang.ref.WeakReference;
import java.util.HashSet;
import java.util.Set;

import android.content.Context;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Message;
import android.util.SparseArray;

import com.android.yummycookies.BuildConfig;
import com.android.yummycookies.model.commonModel.ObserversPool;
import com.android.yummycookies.storage.BitmapStorage;
import com.android.yummycookies.util.Constant;
import com.android.yummycookies.util.Event;
import com.android.yummycookies.util.Logger;

/**
 * Classe permettant le redimensionnement asynchrone de bitmap
 * 
 * @author julien
 * 
 */
public class ScaleBitmapThread extends Thread {

	public static final String TAG = ScaleBitmapThread.class.getName();

	/*****************************************************************
	 * Constantes de redimensionnement de bitmap
	 *****************************************************************/

	/**
	 * Constante indiquant le debut du redimensionnement des bitmap
	 */
	public static final int START_SCALING_BITMAPS = Event.START_SCALING_BITMAPS;
	/**
	 * Constante indiquant qu'un bitmap a ete redimensionne
	 */
	public static final int BITMAP_SCALED = Event.BITMAP_SCALED;
	/**
	 * Constante indiquant qu'un bitmap n'a pas pu etre redimensionne
	 */
	public static final int BITMAP_NOT_SCALED = Event.BITMAP_NOT_SCALED;
	/**
	 * Constante indiquant la fin du redimensionnement des bitmap
	 */
	public static final int END_SCALING_BITMAPS = Event.END_SCALING_BITMAPS;
	/**
	 * Constante indiquant que le bitmap a redimensionner n'est pas present dans
	 * le BitmapStorage
	 */
	public static final int BITMAP_NOT_IN_STORAGE = Event.BITMAP_NOT_IN_STORAGE;

	/*****************************************************************
	 * Contexte de persistance
	 *****************************************************************/

	/** BitmapStorage applicatif */
	private final BitmapStorage bitmapStorage;

	/*****************************************************************
	 * Configuration du redimensionnement
	 *****************************************************************/

	/** valeur de la hauteur du redimensionnement */
	private final int heightScale;
	/** valeur de la largeur du redimensionnement */
	private final int widthScale;

	/*****************************************************************
	 * Collections representant l'avancement du redimensionnement des bitmap
	 *****************************************************************/

	/** Set contenant les identifiants des bitmap a redimensionner */
	private final Set<Integer> bitmapsToScale;
	/** Map contenant les bitmap qui ont ete redimensionnes */
	private final SparseArray<Bitmap> scaledBitmaps = new SparseArray<Bitmap>();

	/** Ensemble des identifiants des bitmap en cours de redimensionnement */
	private final Set<Integer> scalingBitmaps;

	/******************************************************************
	 * Gestion des messages
	 ******************************************************************/
	private final ObserversPool observablePool = new ObserversPool();

	/**
	 * Constructeur du thread de redimensionnemnt de bitmap
	 * 
	 * @param bitmapStorage
	 *            le bitmapStorage qui contiendra les bitmap
	 * @param height
	 *            la hauteur du redimensionnement
	 * @param width
	 *            la largeur du redimensionnement
	 * @param scalingBitmaps
	 *            un ensemble d'id de bitmap permetant d'eviter de
	 *            redimensionner plusieurs fois un bitmap
	 * @param bitmapId
	 *            un identifiant de bitmap a redimensionner
	 */
	public ScaleBitmapThread(BitmapStorage bitmapStorage, int height,
			int width, Set<Integer> scalingBitmaps, int bitmapId) {

		this.bitmapStorage = bitmapStorage;
		this.scalingBitmaps = scalingBitmaps;
		this.heightScale = height;
		this.widthScale = width;
		this.bitmapsToScale = new HashSet<Integer>();
		this.bitmapsToScale.add(bitmapId);

		Logger.logDebug(TAG, TAG + " created");
	}

	/**
	 * Constructeur du thread de redimensionnemnt de bitmap
	 * 
	 * @param context
	 *            le context applicatif
	 * @param bitmapStorage
	 *            le bitmapStorage qui contiendra les bitmap
	 * @param height
	 *            la hauteur du redimensionnement
	 * @param width
	 *            la largeur du redimensionnement
	 * @param scalingBitmaps
	 *            un ensemble d'id de bitmap permetant d'eviter de
	 *            redimensionner plusieurs fois un bitmap
	 * @param bitmapIds
	 *            un ensemble d'identifiants de bitmap a redimensionner
	 */
	public ScaleBitmapThread(BitmapStorage bitmapStorage, int height,
			int width, Set<Integer> scalingBitmaps, Set<Integer> bitmapIds) {

		this.bitmapStorage = bitmapStorage;
		this.heightScale = height;
		this.widthScale = width;
		this.scalingBitmaps = scalingBitmaps;
		this.bitmapsToScale = bitmapIds;

		Logger.logDebug(TAG, TAG + " created");
	}

	/*****************************************************************
	 * Getteurs
	 *****************************************************************/

	/**
	 * Renvoie le pool d'observeurs
	 * 
	 * @return un pool d'observeurs
	 */
	public ObserversPool getObservablePool() {
		return observablePool;
	}

	@Override
	public void run() {
		sendStartMessage();
		// redimensionne tous les bitmap
		for (Integer bitmapId : bitmapsToScale) {
			// redimensionnement du bitmap si il n'est pas dans le
			// bitmapStorage
			if (!bitmapStorage.isScaledBitmapStored(bitmapId)) {
				// ajoute l'id du bitmap dans l'ensemble des bitmap
				// en cours de redimensionnement
				boolean isScaling = false;
				synchronized (scalingBitmaps) {
					isScaling = scalingBitmaps.contains(bitmapId);
					if (!isScaling) {
						scalingBitmaps.add(bitmapId);
					}
				}

				// si le bitmap n'est pas en cours de redimensionnement
				if (!isScaling) {
					// on recupere le bitmap a redimensionner
					Bitmap b = bitmapStorage.get(bitmapId);
					if (b != null) {
						Bitmap bitmapScaled = Bitmap.createScaledBitmap(b,
								widthScale, heightScale, false);
						// ajout du bitmap dans les bitmap redimensionnes
						scaledBitmaps.put(bitmapId, bitmapScaled);

						// envoie du message bitmap redimensionne ou non
						if (bitmapScaled != null) {
							// ajout du bitmap redimensionne dans le
							// bitmapStorage
							bitmapStorage.put(bitmapId, b);
							sendScaledMessage(BITMAP_SCALED, bitmapId,
									bitmapScaled);
						} else {
							sendScaledMessage(BITMAP_NOT_SCALED, bitmapId, null);
						}

						// suppression du bitmap des bitmap en cours de
						// redimensionnement
						scalingBitmaps.remove(bitmapId);

					} else {
						sendScaledMessage(BITMAP_NOT_IN_STORAGE, bitmapId, null);
					}
				} else {
					// le bitmap est deja en cours de redimensionnement dans un
					// autre
					// thread
					Logger.logDebug(TAG, "Bitmap already scaling", bitmapId);
				}

			} else {
				Logger.logDebug(TAG, "Bitmap already scaled", bitmapId);
			}
		}
		sendEndMessage();
	}

	/**
	 * Envoie un message de debut de redimensionnement de bitmap obj: l'ensemble
	 * des id des bitmap a redimensionner
	 */
	private void sendStartMessage() {
		Logger.logDebug(TAG, "Send start message", bitmapsToScale);
		for (WeakReference<Handler> wh : observablePool.getObservers()) {
			Handler h = wh.get();
			if (h != null) {
				Message m = h.obtainMessage();
				m.what = START_SCALING_BITMAPS;
				m.obj = bitmapsToScale;
				h.sendMessage(m);
			}
		}
	}

	/**
	 * Envoie un message de redimensionnement de bitmap arg1: id du bitmap
	 * redimensionne obj: bitmap redimensionne
	 * 
	 * @param event
	 *            l'etat de reussite du redimensionnement du bitmap
	 * @param bitmapId
	 *            l'id du bitmap redimensionne
	 * @param bitmap
	 *            l'objet bitmap redimensionne (null si innexistant)
	 */
	private void sendScaledMessage(int event, int bitmapId, Bitmap bitmap) {
		if (BuildConfig.DEBUG) {
			String state = "failed";
			switch (event) {
			case BITMAP_SCALED:
				state = "succeded";
				break;
			default:
				break;
			}
			Logger.logDebug(TAG, "Send scale message: " + state, bitmapId);
		}
		for (WeakReference<Handler> wh : observablePool.getObservers()) {
			Handler h = wh.get();
			if (h != null) {
				Message m = h.obtainMessage();
				m.what = event;
				m.arg1 = bitmapId;
				m.obj = bitmap;
				h.sendMessage(m);
			}
		}
	}

	/**
	 * Envoie un message de fin de redimensionnement de bitmap obj: un
	 * SparseArray de bitmap redimensionnes
	 */
	private void sendEndMessage() {
		Logger.logDebug(TAG, "Send end message", scaledBitmaps);
		for (WeakReference<Handler> wh : observablePool.getObservers()) {
			Handler h = wh.get();
			if (h != null) {
				Message m = new Message();
				m.what = END_SCALING_BITMAPS;
				m.obj = scaledBitmaps;
				h.sendMessage(m);
			}
		}
	}
}
